@@ -8,7 +8,7 @@ module Agents |
||
8 | 8 |
|
9 | 9 |
This Agent will output data at: |
10 | 10 |
|
11 |
- `https://#{ENV['DOMAIN']}/users/#{user.id}/web_requests/#{id || '<id>'}/:secret.xml` |
|
11 |
+ `https://#{ENV['DOMAIN']}#{Rails.application.routes.url_helpers.web_requests_path(agent_id: ':id', user_id: user_id, secret: ':secret', format: :xml)}` |
|
12 | 12 |
|
13 | 13 |
where `:secret` is one of the allowed secrets specified in your options and the extension can be `xml` or `json`. |
14 | 14 |
|
@@ -19,9 +19,9 @@ module Agents |
||
19 | 19 |
|
20 | 20 |
* `secrets` - An array of tokens that the requestor must provide for light-weight authentication. |
21 | 21 |
* `expected_receive_period_in_days` - How often you expect data to be received by this Agent from other Agents. |
22 |
- * `template` - A JSON object representing a mapping between item output keys and incoming event values. Use [Liquid](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to format the values. The `item` key will be repeated for every Event. The `pubDate` key for each item will have the creation time of the Event unless given. |
|
22 |
+ * `template` - A JSON object representing a mapping between item output keys and incoming event values. Use [Liquid](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to format the values. Values of the `link`, `title`, `description` and `icon` keys will be put into the \\<channel\\> section of RSS output. The `item` key will be repeated for every Event. The `pubDate` key for each item will have the creation time of the Event unless given. |
|
23 | 23 |
* `events_to_show` - The number of events to output in RSS or JSON. (default: `40`) |
24 |
- * `ttl` - A value for the <ttl> element in RSS output. (default: `60`) |
|
24 |
+ * `ttl` - A value for the \\<ttl\\> element in RSS output. (default: `60`) |
|
25 | 25 |
|
26 | 26 |
If you'd like to output RSS tags with attributes, such as `enclosure`, use something like the following in your `template`: |
27 | 27 |
|
@@ -39,6 +39,13 @@ module Agents |
||
39 | 39 |
}, |
40 | 40 |
"_contents": "tag contents (can be an object for nesting)" |
41 | 41 |
} |
42 |
+ |
|
43 |
+ # Liquid Templating |
|
44 |
+ |
|
45 |
+ In Liquid templating, the following variable is available: |
|
46 |
+ |
|
47 |
+ * `events`: An array of events being output, sorted in descending order up to `events_to_show` in number. For example, if source events contain a site title in the `site_title` key, you can refer to it in `template.title` by putting `{{events.first.site_title}}`. |
|
48 |
+ |
|
42 | 49 |
MD |
43 | 50 |
end |
44 | 51 |
|
@@ -63,7 +70,17 @@ module Agents |
||
63 | 70 |
end |
64 | 71 |
|
65 | 72 |
def validate_options |
66 |
- unless options['secrets'].is_a?(Array) && options['secrets'].length > 0 |
|
73 |
+ if options['secrets'].is_a?(Array) && options['secrets'].length > 0 |
|
74 |
+ options['secrets'].each do |secret| |
|
75 |
+ case secret |
|
76 |
+ when %r{[/.]} |
|
77 |
+ errors.add(:base, "secret may not contain a slash or dot") |
|
78 |
+ when String |
|
79 |
+ else |
|
80 |
+ errors.add(:base, "secret must be a string") |
|
81 |
+ end |
|
82 |
+ end |
|
83 |
+ else |
|
67 | 84 |
errors.add(:base, "Please specify one or more secrets for 'authenticating' incoming feed requests") |
68 | 85 |
end |
69 | 86 |
|
@@ -92,15 +109,39 @@ module Agents |
||
92 | 109 |
interpolated['template']['link'].presence || "https://#{ENV['DOMAIN']}" |
93 | 110 |
end |
94 | 111 |
|
112 |
+ def feed_url(options = {}) |
|
113 |
+ feed_link + Rails.application.routes.url_helpers. |
|
114 |
+ web_requests_path(agent_id: id || ':id', |
|
115 |
+ user_id: user_id, |
|
116 |
+ secret: options[:secret], |
|
117 |
+ format: options[:format]) |
|
118 |
+ end |
|
119 |
+ |
|
120 |
+ def feed_icon |
|
121 |
+ interpolated['template']['icon'].presence || feed_link + '/favicon.ico' |
|
122 |
+ end |
|
123 |
+ |
|
95 | 124 |
def feed_description |
96 | 125 |
interpolated['template']['description'].presence || "A feed of Events received by the '#{name}' Huginn Agent" |
97 | 126 |
end |
98 | 127 |
|
99 | 128 |
def receive_web_request(params, method, format) |
100 |
- if interpolated['secrets'].include?(params['secret']) |
|
101 |
- items = received_events.order('id desc').limit(events_to_show).map do |event| |
|
129 |
+ unless interpolated['secrets'].include?(params['secret']) |
|
130 |
+ if format =~ /json/ |
|
131 |
+ return [{ error: "Not Authorized" }, 401] |
|
132 |
+ else |
|
133 |
+ return ["Not Authorized", 401] |
|
134 |
+ end |
|
135 |
+ end |
|
136 |
+ |
|
137 |
+ source_events = received_events.order(id: :desc).limit(events_to_show).to_a |
|
138 |
+ |
|
139 |
+ interpolation_context.stack do |
|
140 |
+ interpolation_context['events'] = source_events |
|
141 |
+ |
|
142 |
+ items = source_events.map do |event| |
|
102 | 143 |
interpolated = interpolate_options(options['template']['item'], event) |
103 |
- interpolated['guid'] = {'_attributes' => {'isPermaLink' => 'false'}, |
|
144 |
+ interpolated['guid'] = {'_attributes' => {'isPermaLink' => 'false'}, |
|
104 | 145 |
'_contents' => interpolated['guid'].presence || event.id} |
105 | 146 |
date_string = interpolated['pubDate'].to_s |
106 | 147 |
date = |
@@ -128,12 +169,13 @@ module Agents |
||
128 | 169 |
<?xml version="1.0" encoding="UTF-8" ?> |
129 | 170 |
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> |
130 | 171 |
<channel> |
131 |
- <atom:link href="#{feed_link.encode(:xml => :text)}/users/#{user.id}/web_requests/#{id || '<id>'}/#{params['secret']}.xml" rel="self" type="application/rss+xml" /> |
|
132 |
- <title>#{feed_title.encode(:xml => :text)}</title> |
|
133 |
- <description>#{feed_description.encode(:xml => :text)}</description> |
|
134 |
- <link>#{feed_link.encode(:xml => :text)}</link> |
|
135 |
- <lastBuildDate>#{Time.now.rfc2822.to_s.encode(:xml => :text)}</lastBuildDate> |
|
136 |
- <pubDate>#{Time.now.rfc2822.to_s.encode(:xml => :text)}</pubDate> |
|
172 |
+ <atom:link href=#{feed_url(secret: params['secret'], format: :xml).encode(xml: :attr)} rel="self" type="application/rss+xml" /> |
|
173 |
+ <atom:icon>#{feed_icon.encode(xml: :text)}</atom:icon> |
|
174 |
+ <title>#{feed_title.encode(xml: :text)}</title> |
|
175 |
+ <description>#{feed_description.encode(xml: :text)}</description> |
|
176 |
+ <link>#{feed_link.encode(xml: :text)}</link> |
|
177 |
+ <lastBuildDate>#{Time.now.rfc2822.to_s.encode(xml: :text)}</lastBuildDate> |
|
178 |
+ <pubDate>#{Time.now.rfc2822.to_s.encode(xml: :text)}</pubDate> |
|
137 | 179 |
<ttl>#{feed_ttl}</ttl> |
138 | 180 |
|
139 | 181 |
XML |
@@ -147,12 +189,6 @@ module Agents |
||
147 | 189 |
|
148 | 190 |
return [content, 200, 'text/xml'] |
149 | 191 |
end |
150 |
- else |
|
151 |
- if format =~ /json/ |
|
152 |
- return [{ :error => "Not Authorized" }, 401] |
|
153 |
- else |
|
154 |
- return ["Not Authorized", 401] |
|
155 |
- end |
|
156 | 192 |
end |
157 | 193 |
end |
158 | 194 |
|
@@ -34,8 +34,18 @@ describe Agents::DataOutputAgent do |
||
34 | 34 |
expect(agent).not_to be_valid |
35 | 35 |
agent.options[:secrets] = "foo" |
36 | 36 |
expect(agent).not_to be_valid |
37 |
+ agent.options[:secrets] = "foo/bar" |
|
38 |
+ expect(agent).not_to be_valid |
|
39 |
+ agent.options[:secrets] = "foo.xml" |
|
40 |
+ expect(agent).not_to be_valid |
|
41 |
+ agent.options[:secrets] = false |
|
42 |
+ expect(agent).not_to be_valid |
|
37 | 43 |
agent.options[:secrets] = [] |
38 | 44 |
expect(agent).not_to be_valid |
45 |
+ agent.options[:secrets] = ["foo.xml"] |
|
46 |
+ expect(agent).not_to be_valid |
|
47 |
+ agent.options[:secrets] = ["hello", true] |
|
48 |
+ expect(agent).not_to be_valid |
|
39 | 49 |
agent.options[:secrets] = ["hello"] |
40 | 50 |
expect(agent).to be_valid |
41 | 51 |
agent.options[:secrets] = ["hello", "world"] |
@@ -83,9 +93,10 @@ describe Agents::DataOutputAgent do |
||
83 | 93 |
expect(status).to eq(200) |
84 | 94 |
end |
85 | 95 |
|
86 |
- describe "outputtng events as RSS and JSON" do |
|
96 |
+ describe "outputting events as RSS and JSON" do |
|
87 | 97 |
let!(:event1) do |
88 | 98 |
agents(:bob_website_agent).create_event :payload => { |
99 |
+ "site_title" => "XKCD", |
|
89 | 100 |
"url" => "http://imgs.xkcd.com/comics/evolving.png", |
90 | 101 |
"title" => "Evolving", |
91 | 102 |
"hovertext" => "Biologists play reverse Pokemon, trying to avoid putting any one team member on the front lines long enough for the experience to cause evolution." |
@@ -94,6 +105,7 @@ describe Agents::DataOutputAgent do |
||
94 | 105 |
|
95 | 106 |
let!(:event2) do |
96 | 107 |
agents(:bob_website_agent).create_event :payload => { |
108 |
+ "site_title" => "XKCD", |
|
97 | 109 |
"url" => "http://imgs.xkcd.com/comics/evolving2.png", |
98 | 110 |
"title" => "Evolving again", |
99 | 111 |
"date" => '', |
@@ -103,6 +115,7 @@ describe Agents::DataOutputAgent do |
||
103 | 115 |
|
104 | 116 |
let!(:event3) do |
105 | 117 |
agents(:bob_website_agent).create_event :payload => { |
118 |
+ "site_title" => "XKCD", |
|
106 | 119 |
"url" => "http://imgs.xkcd.com/comics/evolving0.png", |
107 | 120 |
"title" => "Evolving yet again with a past date", |
108 | 121 |
"date" => '2014/05/05', |
@@ -119,7 +132,8 @@ describe Agents::DataOutputAgent do |
||
119 | 132 |
<?xml version="1.0" encoding="UTF-8" ?> |
120 | 133 |
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> |
121 | 134 |
<channel> |
122 |
- <atom:linkhref="https://yoursite.com/users/#{agent.user.id}/web_requests/#{agent.id}/secret1.xml" rel="self" type="application/rss+xml"/> |
|
135 |
+ <atom:link href="https://yoursite.com/users/#{agent.user.id}/web_requests/#{agent.id}/secret1.xml" rel="self" type="application/rss+xml"/> |
|
136 |
+ <atom:icon>https://yoursite.com/favicon.ico</atom:icon> |
|
123 | 137 |
<title>XKCD comics as a feed</title> |
124 | 138 |
<description>This is a feed of recent XKCD comics, generated by Huginn</description> |
125 | 139 |
<link>https://yoursite.com</link> |
@@ -194,6 +208,43 @@ describe Agents::DataOutputAgent do |
||
194 | 208 |
] |
195 | 209 |
}) |
196 | 210 |
end |
211 |
+ |
|
212 |
+ describe "interpolating \"events\"" do |
|
213 |
+ before do |
|
214 |
+ agent.options['template']['title'] = "XKCD comics as a feed{% if events.first.site_title %} ({{events.first.site_title}}){% endif %}" |
|
215 |
+ agent.save! |
|
216 |
+ end |
|
217 |
+ |
|
218 |
+ it "can output RSS" do |
|
219 |
+ stub(agent).feed_link { "https://yoursite.com" } |
|
220 |
+ content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml') |
|
221 |
+ expect(status).to eq(200) |
|
222 |
+ expect(content_type).to eq('text/xml') |
|
223 |
+ expect(Nokogiri(content).at('/rss/channel/title/text()').text).to eq('XKCD comics as a feed (XKCD)') |
|
224 |
+ end |
|
225 |
+ |
|
226 |
+ it "can output JSON" do |
|
227 |
+ content, status, content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json') |
|
228 |
+ expect(status).to eq(200) |
|
229 |
+ |
|
230 |
+ expect(content['title']).to eq('XKCD comics as a feed (XKCD)') |
|
231 |
+ end |
|
232 |
+ end |
|
233 |
+ |
|
234 |
+ describe "with a specified icon" do |
|
235 |
+ before do |
|
236 |
+ agent.options['template']['icon'] = 'https://somesite.com/icon.png' |
|
237 |
+ agent.save! |
|
238 |
+ end |
|
239 |
+ |
|
240 |
+ it "can output RSS" do |
|
241 |
+ stub(agent).feed_link { "https://yoursite.com" } |
|
242 |
+ content, status, content_type = agent.receive_web_request({ 'secret' => 'secret1' }, 'get', 'text/xml') |
|
243 |
+ expect(status).to eq(200) |
|
244 |
+ expect(content_type).to eq('text/xml') |
|
245 |
+ expect(Nokogiri(content).at('/rss/channel/atom:icon/text()').text).to eq('https://somesite.com/icon.png') |
|
246 |
+ end |
|
247 |
+ end |
|
197 | 248 |
end |
198 | 249 |
|
199 | 250 |
describe "outputting nesting" do |
@@ -294,7 +345,8 @@ describe Agents::DataOutputAgent do |
||
294 | 345 |
<?xml version="1.0" encoding="UTF-8" ?> |
295 | 346 |
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> |
296 | 347 |
<channel> |
297 |
- <atom:linkhref="https://yoursite.com/users/#{agent.user.id}/web_requests/#{agent.id}/secret1.xml" rel="self" type="application/rss+xml"/> |
|
348 |
+ <atom:link href="https://yoursite.com/users/#{agent.user.id}/web_requests/#{agent.id}/secret1.xml" rel="self" type="application/rss+xml"/> |
|
349 |
+ <atom:icon>https://yoursite.com/favicon.ico</atom:icon> |
|
298 | 350 |
<title>XKCD comics as a feed</title> |
299 | 351 |
<description>This is a feed of recent XKCD comics, generated by Huginn</description> |
300 | 352 |
<link>https://yoursite.com</link> |